lib: add shake / fips202
authorFelix Fietkau <[email protected]>
Thu, 10 Jul 2025 20:34:45 +0000 (22:34 +0200)
committerFelix Fietkau <[email protected]>
Sat, 12 Jul 2025 08:33:27 +0000 (10:33 +0200)
Used for ML-DSA

Signed-off-by: Felix Fietkau <[email protected]>
CMakeLists.txt
shake.c [new file with mode: 0644]
shake.h [new file with mode: 0644]

index 8d2b29c1663406419ea7b96ac4a1df366fa29b33..3d9dd37b0a5e276eda14ead03374498564aaf466 100644 (file)
@@ -47,7 +47,7 @@ ELSE()
 ENDIF()
 
 ADD_DEFINITIONS(-DMLK_CONFIG_USE_NATIVE_BACKEND_ARITH)
-ADD_LIBRARY(unet SHARED curve25519.c siphash.c sha512.c fprime.c f25519.c ed25519.c edsign.c auth-data.c chacha20.c pex-msg.c utils.c stun.c random.c sntrup761.c)
+ADD_LIBRARY(unet SHARED curve25519.c siphash.c sha512.c fprime.c f25519.c ed25519.c edsign.c auth-data.c chacha20.c pex-msg.c utils.c stun.c random.c sntrup761.c shake.c)
 TARGET_LINK_LIBRARIES(unet ubox)
 
 ADD_EXECUTABLE(unetd ${SOURCES})
diff --git a/shake.c b/shake.c
new file mode 100644 (file)
index 0000000..2aa1e6d
--- /dev/null
+++ b/shake.c
@@ -0,0 +1,1010 @@
+/*
+ * Copyright (c) The mldsa-native project authors
+ * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
+ */
+/* Based on the public domain implementation in crypto_hash/keccakc512/simple/
+ * from http://bench.cr.yp.to/supercop.html by Ronny Van Keer and the public
+ * domain "TweetFips202" implementation from https://twitter.com/tweetfips202 by
+ * Gilles Van Assche, Daniel J. Bernstein, and Peter Schwabe */
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "shake.h"
+#include "utils.h"
+
+#define NROUNDS 24
+#define ROL(a, offset) ((a << offset) ^ (a >> (64 - offset)))
+
+#define MLD_KECCAK_NROUNDS 24
+#define MLD_KECCAK_ROL(a, offset) ((a << offset) ^ (a >> (64 - offset)))
+
+static void
+mld_keccakf1600_extract_bytes(uint64_t *state, unsigned char *data,
+                             unsigned offset, unsigned length)
+{
+  unsigned i;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+  uint8_t *state_ptr = (uint8_t *)state + offset;
+  for (i = 0; i < length; i++)
+  {
+    data[i] = state_ptr[i];
+  }
+#else
+  /* Portable version */
+  for (i = 0; i < length; i++)
+  {
+    data[i] = (state[(offset + i) >> 3] >> (8 * ((offset + i) & 0x07))) & 0xFF;
+  }
+#endif
+}
+
+static void
+mld_keccakf1600_xor_bytes(uint64_t *state, const unsigned char *data,
+                          unsigned offset, unsigned length)
+{
+  unsigned i;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+  uint8_t *state_ptr = (uint8_t *)state + offset;
+  for (i = 0; i < length; i++)
+  {
+    state_ptr[i] ^= data[i];
+  }
+#else
+  /* Portable version */
+  for (i = 0; i < length; i++)
+  {
+    state[(offset + i) >> 3] ^= (uint64_t)data[i]
+                                << (8 * ((offset + i) & 0x07));
+  }
+#endif
+}
+
+
+static const uint64_t mld_KeccakF_RoundConstants[MLD_KECCAK_NROUNDS] = {
+    (uint64_t)0x0000000000000001ULL, (uint64_t)0x0000000000008082ULL,
+    (uint64_t)0x800000000000808aULL, (uint64_t)0x8000000080008000ULL,
+    (uint64_t)0x000000000000808bULL, (uint64_t)0x0000000080000001ULL,
+    (uint64_t)0x8000000080008081ULL, (uint64_t)0x8000000000008009ULL,
+    (uint64_t)0x000000000000008aULL, (uint64_t)0x0000000000000088ULL,
+    (uint64_t)0x0000000080008009ULL, (uint64_t)0x000000008000000aULL,
+    (uint64_t)0x000000008000808bULL, (uint64_t)0x800000000000008bULL,
+    (uint64_t)0x8000000000008089ULL, (uint64_t)0x8000000000008003ULL,
+    (uint64_t)0x8000000000008002ULL, (uint64_t)0x8000000000000080ULL,
+    (uint64_t)0x000000000000800aULL, (uint64_t)0x800000008000000aULL,
+    (uint64_t)0x8000000080008081ULL, (uint64_t)0x8000000000008080ULL,
+    (uint64_t)0x0000000080000001ULL, (uint64_t)0x8000000080008008ULL};
+
+static void
+mld_keccakf1600_permute(uint64_t *state)
+{
+  unsigned round;
+
+  uint64_t Aba, Abe, Abi, Abo, Abu;
+  uint64_t Aga, Age, Agi, Ago, Agu;
+  uint64_t Aka, Ake, Aki, Ako, Aku;
+  uint64_t Ama, Ame, Ami, Amo, Amu;
+  uint64_t Asa, Ase, Asi, Aso, Asu;
+  uint64_t BCa, BCe, BCi, BCo, BCu;
+  uint64_t Da, De, Di, Do, Du;
+  uint64_t Eba, Ebe, Ebi, Ebo, Ebu;
+  uint64_t Ega, Ege, Egi, Ego, Egu;
+  uint64_t Eka, Eke, Eki, Eko, Eku;
+  uint64_t Ema, Eme, Emi, Emo, Emu;
+  uint64_t Esa, Ese, Esi, Eso, Esu;
+
+  /* copyFromState(A, state) */
+  Aba = state[0];
+  Abe = state[1];
+  Abi = state[2];
+  Abo = state[3];
+  Abu = state[4];
+  Aga = state[5];
+  Age = state[6];
+  Agi = state[7];
+  Ago = state[8];
+  Agu = state[9];
+  Aka = state[10];
+  Ake = state[11];
+  Aki = state[12];
+  Ako = state[13];
+  Aku = state[14];
+  Ama = state[15];
+  Ame = state[16];
+  Ami = state[17];
+  Amo = state[18];
+  Amu = state[19];
+  Asa = state[20];
+  Ase = state[21];
+  Asi = state[22];
+  Aso = state[23];
+  Asu = state[24];
+
+  for (round = 0; round < MLD_KECCAK_NROUNDS; round += 2)
+  {
+    /* prepareTheta */
+    BCa = Aba ^ Aga ^ Aka ^ Ama ^ Asa;
+    BCe = Abe ^ Age ^ Ake ^ Ame ^ Ase;
+    BCi = Abi ^ Agi ^ Aki ^ Ami ^ Asi;
+    BCo = Abo ^ Ago ^ Ako ^ Amo ^ Aso;
+    BCu = Abu ^ Agu ^ Aku ^ Amu ^ Asu;
+
+    /* thetaRhoPiChiIotaPrepareTheta(round, A, E) */
+    Da = BCu ^ MLD_KECCAK_ROL(BCe, 1);
+    De = BCa ^ MLD_KECCAK_ROL(BCi, 1);
+    Di = BCe ^ MLD_KECCAK_ROL(BCo, 1);
+    Do = BCi ^ MLD_KECCAK_ROL(BCu, 1);
+    Du = BCo ^ MLD_KECCAK_ROL(BCa, 1);
+
+    Aba ^= Da;
+    BCa = Aba;
+    Age ^= De;
+    BCe = MLD_KECCAK_ROL(Age, 44);
+    Aki ^= Di;
+    BCi = MLD_KECCAK_ROL(Aki, 43);
+    Amo ^= Do;
+    BCo = MLD_KECCAK_ROL(Amo, 21);
+    Asu ^= Du;
+    BCu = MLD_KECCAK_ROL(Asu, 14);
+    Eba = BCa ^ ((~BCe) & BCi);
+    Eba ^= (uint64_t)mld_KeccakF_RoundConstants[round];
+    Ebe = BCe ^ ((~BCi) & BCo);
+    Ebi = BCi ^ ((~BCo) & BCu);
+    Ebo = BCo ^ ((~BCu) & BCa);
+    Ebu = BCu ^ ((~BCa) & BCe);
+
+    Abo ^= Do;
+    BCa = MLD_KECCAK_ROL(Abo, 28);
+    Agu ^= Du;
+    BCe = MLD_KECCAK_ROL(Agu, 20);
+    Aka ^= Da;
+    BCi = MLD_KECCAK_ROL(Aka, 3);
+    Ame ^= De;
+    BCo = MLD_KECCAK_ROL(Ame, 45);
+    Asi ^= Di;
+    BCu = MLD_KECCAK_ROL(Asi, 61);
+    Ega = BCa ^ ((~BCe) & BCi);
+    Ege = BCe ^ ((~BCi) & BCo);
+    Egi = BCi ^ ((~BCo) & BCu);
+    Ego = BCo ^ ((~BCu) & BCa);
+    Egu = BCu ^ ((~BCa) & BCe);
+
+    Abe ^= De;
+    BCa = MLD_KECCAK_ROL(Abe, 1);
+    Agi ^= Di;
+    BCe = MLD_KECCAK_ROL(Agi, 6);
+    Ako ^= Do;
+    BCi = MLD_KECCAK_ROL(Ako, 25);
+    Amu ^= Du;
+    BCo = MLD_KECCAK_ROL(Amu, 8);
+    Asa ^= Da;
+    BCu = MLD_KECCAK_ROL(Asa, 18);
+    Eka = BCa ^ ((~BCe) & BCi);
+    Eke = BCe ^ ((~BCi) & BCo);
+    Eki = BCi ^ ((~BCo) & BCu);
+    Eko = BCo ^ ((~BCu) & BCa);
+    Eku = BCu ^ ((~BCa) & BCe);
+
+    Abu ^= Du;
+    BCa = MLD_KECCAK_ROL(Abu, 27);
+    Aga ^= Da;
+    BCe = MLD_KECCAK_ROL(Aga, 36);
+    Ake ^= De;
+    BCi = MLD_KECCAK_ROL(Ake, 10);
+    Ami ^= Di;
+    BCo = MLD_KECCAK_ROL(Ami, 15);
+    Aso ^= Do;
+    BCu = MLD_KECCAK_ROL(Aso, 56);
+    Ema = BCa ^ ((~BCe) & BCi);
+    Eme = BCe ^ ((~BCi) & BCo);
+    Emi = BCi ^ ((~BCo) & BCu);
+    Emo = BCo ^ ((~BCu) & BCa);
+    Emu = BCu ^ ((~BCa) & BCe);
+
+    Abi ^= Di;
+    BCa = MLD_KECCAK_ROL(Abi, 62);
+    Ago ^= Do;
+    BCe = MLD_KECCAK_ROL(Ago, 55);
+    Aku ^= Du;
+    BCi = MLD_KECCAK_ROL(Aku, 39);
+    Ama ^= Da;
+    BCo = MLD_KECCAK_ROL(Ama, 41);
+    Ase ^= De;
+    BCu = MLD_KECCAK_ROL(Ase, 2);
+    Esa = BCa ^ ((~BCe) & BCi);
+    Ese = BCe ^ ((~BCi) & BCo);
+    Esi = BCi ^ ((~BCo) & BCu);
+    Eso = BCo ^ ((~BCu) & BCa);
+    Esu = BCu ^ ((~BCa) & BCe);
+
+    /* prepareTheta */
+    BCa = Eba ^ Ega ^ Eka ^ Ema ^ Esa;
+    BCe = Ebe ^ Ege ^ Eke ^ Eme ^ Ese;
+    BCi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi;
+    BCo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso;
+    BCu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu;
+
+    /* thetaRhoPiChiIotaPrepareTheta(round+1, E, A) */
+    Da = BCu ^ MLD_KECCAK_ROL(BCe, 1);
+    De = BCa ^ MLD_KECCAK_ROL(BCi, 1);
+    Di = BCe ^ MLD_KECCAK_ROL(BCo, 1);
+    Do = BCi ^ MLD_KECCAK_ROL(BCu, 1);
+    Du = BCo ^ MLD_KECCAK_ROL(BCa, 1);
+
+    Eba ^= Da;
+    BCa = Eba;
+    Ege ^= De;
+    BCe = MLD_KECCAK_ROL(Ege, 44);
+    Eki ^= Di;
+    BCi = MLD_KECCAK_ROL(Eki, 43);
+    Emo ^= Do;
+    BCo = MLD_KECCAK_ROL(Emo, 21);
+    Esu ^= Du;
+    BCu = MLD_KECCAK_ROL(Esu, 14);
+    Aba = BCa ^ ((~BCe) & BCi);
+    Aba ^= (uint64_t)mld_KeccakF_RoundConstants[round + 1];
+    Abe = BCe ^ ((~BCi) & BCo);
+    Abi = BCi ^ ((~BCo) & BCu);
+    Abo = BCo ^ ((~BCu) & BCa);
+    Abu = BCu ^ ((~BCa) & BCe);
+
+    Ebo ^= Do;
+    BCa = MLD_KECCAK_ROL(Ebo, 28);
+    Egu ^= Du;
+    BCe = MLD_KECCAK_ROL(Egu, 20);
+    Eka ^= Da;
+    BCi = MLD_KECCAK_ROL(Eka, 3);
+    Eme ^= De;
+    BCo = MLD_KECCAK_ROL(Eme, 45);
+    Esi ^= Di;
+    BCu = MLD_KECCAK_ROL(Esi, 61);
+    Aga = BCa ^ ((~BCe) & BCi);
+    Age = BCe ^ ((~BCi) & BCo);
+    Agi = BCi ^ ((~BCo) & BCu);
+    Ago = BCo ^ ((~BCu) & BCa);
+    Agu = BCu ^ ((~BCa) & BCe);
+
+    Ebe ^= De;
+    BCa = MLD_KECCAK_ROL(Ebe, 1);
+    Egi ^= Di;
+    BCe = MLD_KECCAK_ROL(Egi, 6);
+    Eko ^= Do;
+    BCi = MLD_KECCAK_ROL(Eko, 25);
+    Emu ^= Du;
+    BCo = MLD_KECCAK_ROL(Emu, 8);
+    Esa ^= Da;
+    BCu = MLD_KECCAK_ROL(Esa, 18);
+    Aka = BCa ^ ((~BCe) & BCi);
+    Ake = BCe ^ ((~BCi) & BCo);
+    Aki = BCi ^ ((~BCo) & BCu);
+    Ako = BCo ^ ((~BCu) & BCa);
+    Aku = BCu ^ ((~BCa) & BCe);
+
+    Ebu ^= Du;
+    BCa = MLD_KECCAK_ROL(Ebu, 27);
+    Ega ^= Da;
+    BCe = MLD_KECCAK_ROL(Ega, 36);
+    Eke ^= De;
+    BCi = MLD_KECCAK_ROL(Eke, 10);
+    Emi ^= Di;
+    BCo = MLD_KECCAK_ROL(Emi, 15);
+    Eso ^= Do;
+    BCu = MLD_KECCAK_ROL(Eso, 56);
+    Ama = BCa ^ ((~BCe) & BCi);
+    Ame = BCe ^ ((~BCi) & BCo);
+    Ami = BCi ^ ((~BCo) & BCu);
+    Amo = BCo ^ ((~BCu) & BCa);
+    Amu = BCu ^ ((~BCa) & BCe);
+
+    Ebi ^= Di;
+    BCa = MLD_KECCAK_ROL(Ebi, 62);
+    Ego ^= Do;
+    BCe = MLD_KECCAK_ROL(Ego, 55);
+    Eku ^= Du;
+    BCi = MLD_KECCAK_ROL(Eku, 39);
+    Ema ^= Da;
+    BCo = MLD_KECCAK_ROL(Ema, 41);
+    Ese ^= De;
+    BCu = MLD_KECCAK_ROL(Ese, 2);
+    Asa = BCa ^ ((~BCe) & BCi);
+    Ase = BCe ^ ((~BCi) & BCo);
+    Asi = BCi ^ ((~BCo) & BCu);
+    Aso = BCo ^ ((~BCu) & BCa);
+    Asu = BCu ^ ((~BCa) & BCe);
+  }
+
+  /* copyToState(state, A) */
+  state[0] = Aba;
+  state[1] = Abe;
+  state[2] = Abi;
+  state[3] = Abo;
+  state[4] = Abu;
+  state[5] = Aga;
+  state[6] = Age;
+  state[7] = Agi;
+  state[8] = Ago;
+  state[9] = Agu;
+  state[10] = Aka;
+  state[11] = Ake;
+  state[12] = Aki;
+  state[13] = Ako;
+  state[14] = Aku;
+  state[15] = Ama;
+  state[16] = Ame;
+  state[17] = Ami;
+  state[18] = Amo;
+  state[19] = Amu;
+  state[20] = Asa;
+  state[21] = Ase;
+  state[22] = Asi;
+  state[23] = Aso;
+  state[24] = Asu;
+}
+
+static void
+mld_keccakf1600x4_extract_bytes(uint64_t *state, unsigned char *data0,
+                                unsigned char *data1, unsigned char *data2,
+                                unsigned char *data3, unsigned offset,
+                                unsigned length)
+{
+  mld_keccakf1600_extract_bytes(state + MLD_KECCAK_LANES * 0, data0, offset,
+                                length);
+  mld_keccakf1600_extract_bytes(state + MLD_KECCAK_LANES * 1, data1, offset,
+                                length);
+  mld_keccakf1600_extract_bytes(state + MLD_KECCAK_LANES * 2, data2, offset,
+                                length);
+  mld_keccakf1600_extract_bytes(state + MLD_KECCAK_LANES * 3, data3, offset,
+                                length);
+}
+
+static void
+mld_keccakf1600x4_xor_bytes(uint64_t *state, const unsigned char *data0,
+                            const unsigned char *data1,
+                            const unsigned char *data2,
+                            const unsigned char *data3, unsigned offset,
+                            unsigned length)
+{
+  mld_keccakf1600_xor_bytes(state + MLD_KECCAK_LANES * 0, data0, offset,
+                            length);
+  mld_keccakf1600_xor_bytes(state + MLD_KECCAK_LANES * 1, data1, offset,
+                            length);
+  mld_keccakf1600_xor_bytes(state + MLD_KECCAK_LANES * 2, data2, offset,
+                            length);
+  mld_keccakf1600_xor_bytes(state + MLD_KECCAK_LANES * 3, data3, offset,
+                            length);
+}
+
+static void
+mld_keccakf1600x4_permute(uint64_t *state)
+{
+  mld_keccakf1600_permute(state + MLD_KECCAK_LANES * 0);
+  mld_keccakf1600_permute(state + MLD_KECCAK_LANES * 1);
+  mld_keccakf1600_permute(state + MLD_KECCAK_LANES * 2);
+  mld_keccakf1600_permute(state + MLD_KECCAK_LANES * 3);
+}
+
+/*************************************************
+ * Name:        load64
+ *
+ * Description: Load 8 bytes into uint64_t in little-endian order
+ *
+ * Arguments:   - const uint8_t *x: pointer to input byte array
+ *
+ * Returns the loaded 64-bit unsigned integer
+ **************************************************/
+static uint64_t load64(const uint8_t x[8])
+{
+  unsigned int i;
+  uint64_t r = 0;
+
+  for (i = 0; i < 8; i++)
+  {
+    r |= (uint64_t)x[i] << 8 * i;
+  }
+
+  return r;
+}
+
+/*************************************************
+ * Name:        store64
+ *
+ * Description: Store a 64-bit integer to array of 8 bytes in little-endian
+ *order
+ *
+ * Arguments:   - uint8_t *x: pointer to the output byte array (allocated)
+ *              - uint64_t u: input 64-bit unsigned integer
+ **************************************************/
+static void store64(uint8_t x[8], uint64_t u)
+{
+  unsigned int i;
+
+  for (i = 0; i < 8; i++)
+  {
+    /* Explicitly truncate to uint8_t */
+    x[i] = (uint8_t)((u >> (8 * i)) & 0xFF);
+  }
+}
+
+/*************************************************
+ * Name:        keccak_init
+ *
+ * Description: Initializes the Keccak state.
+ *
+ * Arguments:   - uint64_t *s: pointer to Keccak state
+ **************************************************/
+static void keccak_init(uint64_t s[MLD_KECCAK_LANES])
+{
+  unsigned int i;
+  for (i = 0; i < MLD_KECCAK_LANES; i++)
+  {
+    s[i] = 0;
+  }
+}
+
+/*************************************************
+ * Name:        keccak_absorb
+ *
+ * Description: Absorb step of Keccak; incremental.
+ *
+ * Arguments:   - uint64_t *s: pointer to Keccak state
+ *              - unsigned int pos: position in current block to be absorbed
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ *
+ * Returns new position pos in current block
+ **************************************************/
+static unsigned int keccak_absorb(uint64_t s[MLD_KECCAK_LANES],
+                                  unsigned int pos, unsigned int r,
+                                  const uint8_t *in, size_t inlen)
+{
+  unsigned int i;
+
+  while (pos + inlen >= r)
+  {
+    for (i = pos; i < r; i++)
+    {
+      s[i / 8] ^= (uint64_t)*in++ << 8 * (i % 8);
+    }
+    inlen -= r - pos;
+    mld_keccakf1600_permute(s);
+    pos = 0;
+  }
+
+  for (i = pos; i < pos + inlen; i++)
+  {
+    s[i / 8] ^= (uint64_t)*in++ << 8 * (i % 8);
+  }
+
+  return i;
+}
+
+/*************************************************
+ * Name:        keccak_finalize
+ *
+ * Description: Finalize absorb step.
+ *
+ * Arguments:   - uint64_t *s: pointer to Keccak state
+ *              - unsigned int pos: position in current block to be absorbed
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *              - uint8_t p: domain separation byte
+ **************************************************/
+static void keccak_finalize(uint64_t s[MLD_KECCAK_LANES], unsigned int pos,
+                            unsigned int r, uint8_t p)
+{
+  s[pos / 8] ^= (uint64_t)p << 8 * (pos % 8);
+  s[r / 8 - 1] ^= 1ULL << 63;
+}
+
+/*************************************************
+ * Name:        keccak_squeeze
+ *
+ * Description: Squeeze step of Keccak. Squeezes arbitratrily many bytes.
+ *              Modifies the state. Can be called multiple times to keep
+ *              squeezing, i.e., is incremental.
+ *
+ * Arguments:   - uint8_t *out: pointer to output data
+ *              - size_t outlen: number of bytes to be squeezed (written to out)
+ *              - uint64_t *s: pointer to input/output Keccak state
+ *              - unsigned int pos: number of bytes in current block already
+ *squeezed
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *
+ * Returns new position pos in current block
+ **************************************************/
+static unsigned int keccak_squeeze(uint8_t *out, size_t outlen,
+                                   uint64_t s[MLD_KECCAK_LANES],
+                                   unsigned int pos, unsigned int r)
+{
+  unsigned int i;
+  size_t out_offset = 0;
+
+  /* Reference: This code is re-factored from the reference implementation
+   * to facilitate proof with CBMC and to improve readability.
+   *
+   * Take a mutable copy of outlen to count down the number of bytes
+   * still to squeeze. The initial value of outlen is needed for the CBMC
+   * assigns() clauses. */
+  size_t bytes_to_go = outlen;
+
+  while (bytes_to_go > 0)
+  {
+    if (pos == r)
+    {
+      mld_keccakf1600_permute(s);
+      pos = 0;
+    }
+    for (i = pos; i < r && i < pos + bytes_to_go; i++)
+    {
+      const uint64_t lane = s[i / 8];
+      out[out_offset] = (uint8_t)((lane >> (8 * (i % 8))) & 0xFF);
+      out_offset++;
+    }
+    bytes_to_go -= i - pos;
+    pos = i;
+  }
+
+  return pos;
+}
+
+/*************************************************
+ * Name:        keccak_absorb_once
+ *
+ * Description: Absorb step of Keccak;
+ *              non-incremental, starts by zeroeing the state.
+ *
+ * Arguments:   - uint64_t *s: pointer to (uninitialized) output Keccak state
+ *              - const unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ *              - uint8_t p: domain-separation byte for different Keccak-derived
+ *functions
+ **************************************************/
+static void keccak_absorb_once(uint64_t s[MLD_KECCAK_LANES],
+                               const unsigned int r, const uint8_t *in,
+                               size_t inlen, uint8_t p)
+{
+  unsigned int i;
+
+  for (i = 0; i < MLD_KECCAK_LANES; i++)
+  {
+    s[i] = 0;
+  }
+
+  while (inlen >= r)
+  {
+    for (i = 0; i < r / 8; i++)
+    {
+      s[i] ^= load64(in + 8 * i);
+    }
+    in += r;
+    inlen -= r;
+    mld_keccakf1600_permute(s);
+  }
+
+  for (i = 0; i < inlen; i++)
+  {
+    s[i / 8] ^= (uint64_t)in[i] << 8 * (i % 8);
+  }
+
+  s[i / 8] ^= (uint64_t)p << 8 * (i % 8);
+  s[(r - 1) / 8] ^= 1ULL << 63;
+}
+
+/*************************************************
+ * Name:        keccak_squeezeblocks
+ *
+ * Description: Squeeze step of Keccak. Squeezes full blocks of r bytes each.
+ *              Modifies the state. Can be called multiple times to keep
+ *              squeezing, i.e., is incremental. Assumes zero bytes of current
+ *              block have already been squeezed.
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t nblocks: number of blocks to be squeezed (written to
+ *out)
+ *              - uint64_t *s: pointer to input/output Keccak state
+ *              - unsigned int r: rate in bytes (e.g., 168 for SHAKE128)
+ **************************************************/
+static void keccak_squeezeblocks(uint8_t *out, size_t nblocks,
+                                 uint64_t s[MLD_KECCAK_LANES], unsigned int r)
+{
+  while (nblocks > 0)
+  {
+    mld_keccakf1600_permute(s);
+    mld_keccakf1600_extract_bytes(s, out, 0, r);
+    out += r;
+    nblocks -= 1;
+  }
+}
+
+/*************************************************
+ * Name:        shake128_init
+ *
+ * Description: Initilizes Keccak state for use as SHAKE128 XOF
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) Keccak state
+ **************************************************/
+void shake128_init(keccak_state *state)
+{
+  keccak_init(state->s);
+  state->pos = 0;
+}
+
+/*************************************************
+ * Name:        shake128_absorb
+ *
+ * Description: Absorb step of the SHAKE128 XOF; incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (initialized) output Keccak
+ *state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen)
+{
+  state->pos = keccak_absorb(state->s, state->pos, SHAKE128_RATE, in, inlen);
+}
+
+/*************************************************
+ * Name:        shake128_finalize
+ *
+ * Description: Finalize absorb step of the SHAKE128 XOF.
+ *
+ * Arguments:   - keccak_state *state: pointer to Keccak state
+ **************************************************/
+void shake128_finalize(keccak_state *state)
+{
+  keccak_finalize(state->s, state->pos, SHAKE128_RATE, 0x1F);
+  state->pos = SHAKE128_RATE;
+}
+
+/*************************************************
+ * Name:        shake128_squeeze
+ *
+ * Description: Squeeze step of SHAKE128 XOF. Squeezes arbitraily many
+ *              bytes. Can be called multiple times to keep squeezing.
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t outlen : number of bytes to be squeezed (written to
+ *output)
+ *              - keccak_state *s: pointer to input/output Keccak state
+ **************************************************/
+void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state)
+{
+  state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE128_RATE);
+}
+
+/*************************************************
+ * Name:        shake128_absorb_once
+ *
+ * Description: Initialize, absorb into and finalize SHAKE128 XOF;
+ *non-incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) output Keccak
+ *state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen)
+{
+  keccak_absorb_once(state->s, SHAKE128_RATE, in, inlen, 0x1F);
+  state->pos = SHAKE128_RATE;
+}
+
+/*************************************************
+ * Name:        shake128_squeezeblocks
+ *
+ * Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of
+ *              SHAKE128_RATE bytes each. Can be called multiple times
+ *              to keep squeezing. Assumes new block has not yet been
+ *              started (state->pos = SHAKE128_RATE).
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t nblocks: number of blocks to be squeezed (written to
+ *output)
+ *              - keccak_state *s: pointer to input/output Keccak state
+ **************************************************/
+void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state)
+{
+  keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE);
+}
+
+void shake128_release(keccak_state *state)
+{
+  (void)state;
+  /* TODO: add secure zeroization*/
+}
+
+/*************************************************
+ * Name:        shake256_init
+ *
+ * Description: Initilizes Keccak state for use as SHAKE256 XOF
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) Keccak state
+ **************************************************/
+void shake256_init(keccak_state *state)
+{
+  keccak_init(state->s);
+  state->pos = 0;
+}
+
+/*************************************************
+ * Name:        shake256_absorb
+ *
+ * Description: Absorb step of the SHAKE256 XOF; incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (initialized) output Keccak
+ *state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen)
+{
+  state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen);
+}
+
+/*************************************************
+ * Name:        shake256_finalize
+ *
+ * Description: Finalize absorb step of the SHAKE256 XOF.
+ *
+ * Arguments:   - keccak_state *state: pointer to Keccak state
+ **************************************************/
+void shake256_finalize(keccak_state *state)
+{
+  keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F);
+  state->pos = SHAKE256_RATE;
+}
+
+/*************************************************
+ * Name:        shake256_squeeze
+ *
+ * Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many
+ *              bytes. Can be called multiple times to keep squeezing.
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t outlen : number of bytes to be squeezed (written to
+ *output)
+ *              - keccak_state *s: pointer to input/output Keccak state
+ **************************************************/
+void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state)
+{
+  state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE);
+}
+
+/*************************************************
+ * Name:        shake256_absorb_once
+ *
+ * Description: Initialize, absorb into and finalize SHAKE256 XOF;
+ *non-incremental.
+ *
+ * Arguments:   - keccak_state *state: pointer to (uninitialized) output Keccak
+ *state
+ *              - const uint8_t *in: pointer to input to be absorbed into s
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen)
+{
+  keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F);
+  state->pos = SHAKE256_RATE;
+}
+
+/*************************************************
+ * Name:        shake256_squeezeblocks
+ *
+ * Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of
+ *              SHAKE256_RATE bytes each. Can be called multiple times
+ *              to keep squeezing. Assumes next block has not yet been
+ *              started (state->pos = SHAKE256_RATE).
+ *
+ * Arguments:   - uint8_t *out: pointer to output blocks
+ *              - size_t nblocks: number of blocks to be squeezed (written to
+ *output)
+ *              - keccak_state *s: pointer to input/output Keccak state
+ **************************************************/
+void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state)
+{
+  keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE);
+}
+
+void shake256_release(keccak_state *state)
+{
+  (void)state;
+  /* TODO: add secure zeroization*/
+}
+
+/*************************************************
+ * Name:        shake128
+ *
+ * Description: SHAKE128 XOF with non-incremental API
+ *
+ * Arguments:   - uint8_t *out: pointer to output
+ *              - size_t outlen: requested output length in bytes
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen)
+{
+  size_t nblocks;
+  keccak_state state;
+
+  shake128_absorb_once(&state, in, inlen);
+  nblocks = outlen / SHAKE128_RATE;
+  shake128_squeezeblocks(out, nblocks, &state);
+  outlen -= nblocks * SHAKE128_RATE;
+  out += nblocks * SHAKE128_RATE;
+  shake128_squeeze(out, outlen, &state);
+}
+
+/*************************************************
+ * Name:        shake256
+ *
+ * Description: SHAKE256 XOF with non-incremental API
+ *
+ * Arguments:   - uint8_t *out: pointer to output
+ *              - size_t outlen: requested output length in bytes
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen)
+{
+  size_t nblocks;
+  keccak_state state;
+
+  shake256_absorb_once(&state, in, inlen);
+  nblocks = outlen / SHAKE256_RATE;
+  shake256_squeezeblocks(out, nblocks, &state);
+  outlen -= nblocks * SHAKE256_RATE;
+  out += nblocks * SHAKE256_RATE;
+  shake256_squeeze(out, outlen, &state);
+}
+
+/*************************************************
+ * Name:        sha3_256
+ *
+ * Description: SHA3-256 with non-incremental API
+ *
+ * Arguments:   - uint8_t *h: pointer to output (32 bytes)
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void sha3_256(uint8_t h[SHA3_256_HASHBYTES], const uint8_t *in, size_t inlen)
+{
+  unsigned int i;
+  uint64_t s[MLD_KECCAK_LANES];
+
+  keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06);
+  mld_keccakf1600_permute(s);
+  for (i = 0; i < 4; i++)
+  {
+    store64(h + 8 * i, s[i]);
+  }
+}
+
+/*************************************************
+ * Name:        sha3_512
+ *
+ * Description: SHA3-512 with non-incremental API
+ *
+ * Arguments:   - uint8_t *h: pointer to output (64 bytes)
+ *              - const uint8_t *in: pointer to input
+ *              - size_t inlen: length of input in bytes
+ **************************************************/
+void sha3_512(uint8_t h[SHA3_512_HASHBYTES], const uint8_t *in, size_t inlen)
+{
+  unsigned int i;
+  uint64_t s[MLD_KECCAK_LANES];
+
+  keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06);
+  mld_keccakf1600_permute(s);
+  for (i = 0; i < 8; i++)
+  {
+    store64(h + 8 * i, s[i]);
+  }
+}
+
+
+static void mld_keccak_absorb_once_x4(uint64_t *s, uint32_t r,
+                                      const uint8_t *in0, const uint8_t *in1,
+                                      const uint8_t *in2, const uint8_t *in3,
+                                      size_t inlen, uint8_t p)
+{
+  while (inlen >= r)
+  {
+    mld_keccakf1600x4_xor_bytes(s, in0, in1, in2, in3, 0, r);
+    mld_keccakf1600x4_permute(s);
+
+    in0 += r;
+    in1 += r;
+    in2 += r;
+    in3 += r;
+    inlen -= r;
+  }
+
+  if (inlen > 0)
+  {
+    mld_keccakf1600x4_xor_bytes(s, in0, in1, in2, in3, 0, inlen);
+  }
+
+  if (inlen == r - 1)
+  {
+    p |= 128;
+    mld_keccakf1600x4_xor_bytes(s, &p, &p, &p, &p, inlen, 1);
+  }
+  else
+  {
+    mld_keccakf1600x4_xor_bytes(s, &p, &p, &p, &p, inlen, 1);
+    p = 128;
+    mld_keccakf1600x4_xor_bytes(s, &p, &p, &p, &p, r - 1, 1);
+  }
+}
+
+static void mld_keccak_squeezeblocks_x4(uint8_t *out0, uint8_t *out1,
+                                        uint8_t *out2, uint8_t *out3,
+                                        size_t nblocks, uint64_t *s, uint32_t r)
+{
+  while (nblocks > 0)
+  {
+    mld_keccakf1600x4_permute(s);
+    mld_keccakf1600x4_extract_bytes(s, out0, out1, out2, out3, 0, r);
+
+    out0 += r;
+    out1 += r;
+    out2 += r;
+    out3 += r;
+    nblocks--;
+  }
+}
+
+void mld_shake128x4_absorb_once(mld_shake128x4ctx *state, const uint8_t *in0,
+                                const uint8_t *in1, const uint8_t *in2,
+                                const uint8_t *in3, size_t inlen)
+{
+  memset(state, 0, sizeof(mld_shake128x4ctx));
+  mld_keccak_absorb_once_x4(state->ctx, SHAKE128_RATE, in0, in1, in2, in3,
+                            inlen, 0x1F);
+}
+
+void mld_shake128x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2,
+                                  uint8_t *out3, size_t nblocks,
+                                  mld_shake128x4ctx *state)
+{
+  mld_keccak_squeezeblocks_x4(out0, out1, out2, out3, nblocks, state->ctx,
+                              SHAKE128_RATE);
+}
+
+void mld_shake128x4_init(mld_shake128x4ctx *state) { (void)state; }
+void mld_shake128x4_release(mld_shake128x4ctx *state)
+{
+  /* Specification: Partially implements
+   * @[FIPS203, Section 3.3, Destruction of intermediate values] */
+  (void)state;
+  /*mld_zeroize(state, sizeof(mld_shake128x4ctx));*/
+}
+
+
+void mld_shake256x4_absorb_once(mld_shake256x4ctx *state, const uint8_t *in0,
+                                const uint8_t *in1, const uint8_t *in2,
+                                const uint8_t *in3, size_t inlen)
+{
+  memset(state, 0, sizeof(mld_shake256x4ctx));
+  mld_keccak_absorb_once_x4(state->ctx, SHAKE256_RATE, in0, in1, in2, in3,
+                            inlen, 0x1F);
+}
+
+void mld_shake256x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2,
+                                  uint8_t *out3, size_t nblocks,
+                                  mld_shake256x4ctx *state)
+{
+  mld_keccak_squeezeblocks_x4(out0, out1, out2, out3, nblocks, state->ctx,
+                              SHAKE256_RATE);
+}
+
+void mld_shake256x4_init(mld_shake256x4ctx *state) { (void)state; }
+void mld_shake256x4_release(mld_shake256x4ctx *state)
+{
+  /* Specification: Partially implements
+   * @[FIPS203, Section 3.3, Destruction of intermediate values] */
+  (void)state;
+  /*mld_zeroize(state, sizeof(mld_shake256x4ctx));*/
+}
diff --git a/shake.h b/shake.h
new file mode 100644 (file)
index 0000000..36253e7
--- /dev/null
+++ b/shake.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) The mldsa-native project authors
+ * SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
+ */
+#ifndef MLD_FIPS202_FIPS202_H
+#define MLD_FIPS202_FIPS202_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define SHAKE128_RATE 168
+#define SHAKE256_RATE 136
+#define SHA3_256_RATE 136
+#define SHA3_512_RATE 72
+#define MLD_KECCAK_LANES 25
+#define SHA3_256_HASHBYTES 32
+#define SHA3_512_HASHBYTES 64
+
+#define MLD_KECCAK_LANES 25
+#define MLD_KECCAK_WAY 4
+
+typedef struct
+{
+  uint64_t s[MLD_KECCAK_LANES];
+  unsigned int pos;
+} keccak_state;
+
+/* Context for non-incremental API */
+typedef struct
+{
+  uint64_t ctx[MLD_KECCAK_LANES * MLD_KECCAK_WAY];
+} mld_shake128x4ctx;
+
+typedef struct
+{
+  uint64_t ctx[MLD_KECCAK_LANES * MLD_KECCAK_WAY];
+} mld_shake256x4ctx;
+
+extern const uint64_t KeccakF_RoundConstants[];
+
+void shake128_init(keccak_state *state);
+void shake128_absorb(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake128_finalize(keccak_state *state);
+void shake128_squeeze(uint8_t *out, size_t outlen, keccak_state *state);
+void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen);
+
+void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state);
+void shake128_release(keccak_state *state);
+
+void shake256_init(keccak_state *state);
+void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake256_finalize(keccak_state *state);
+
+void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state);
+void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen);
+void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state);
+void shake256_release(keccak_state *state);
+
+void shake128(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen);
+void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen);;
+
+void sha3_256(uint8_t h[SHA3_256_HASHBYTES], const uint8_t *in, size_t inlen);
+
+void sha3_512(uint8_t h[SHA3_512_HASHBYTES], const uint8_t *in, size_t inlen);
+
+void mld_shake128x4_absorb_once(mld_shake128x4ctx *state, const uint8_t *in0,
+                                const uint8_t *in1, const uint8_t *in2,
+                                const uint8_t *in3, size_t inlen);
+void mld_shake128x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2,
+                                  uint8_t *out3, size_t nblocks,
+                                  mld_shake128x4ctx *state);
+void mld_shake128x4_init(mld_shake128x4ctx *state);
+void mld_shake128x4_release(mld_shake128x4ctx *state);
+void mld_shake256x4_absorb_once(mld_shake256x4ctx *state, const uint8_t *in0,
+                                const uint8_t *in1, const uint8_t *in2,
+                                const uint8_t *in3, size_t inlen);
+void mld_shake256x4_squeezeblocks(uint8_t *out0, uint8_t *out1, uint8_t *out2,
+                                  uint8_t *out3, size_t nblocks,
+                                  mld_shake256x4ctx *state);
+void mld_shake256x4_init(mld_shake256x4ctx *state);
+void mld_shake256x4_release(mld_shake256x4ctx *state);
+
+#endif /* !MLD_FIPS202_FIPS202_H */